<script setup>
import tinymce from 'tinymce/tinymce';
import 'tinymce/themes/silver';
import 'tinymce/icons/default/icons';
import 'tinymce/plugins/advlist';
import 'tinymce/plugins/anchor';
import 'tinymce/plugins/autolink';
import 'tinymce/plugins/autosave';
import 'tinymce/plugins/code';
import 'tinymce/plugins/codesample';
import 'tinymce/plugins/directionality';
import 'tinymce/plugins/fullscreen';
import 'tinymce/plugins/hr';
import 'tinymce/plugins/insertdatetime';
import 'tinymce/plugins/link';
import 'tinymce/plugins/lists';
import 'tinymce/plugins/media';
import 'tinymce/plugins/nonbreaking';
import 'tinymce/plugins/noneditable';
import 'tinymce/plugins/pagebreak';
import 'tinymce/plugins/paste';
import 'tinymce/plugins/preview';
import 'tinymce/plugins/print';
import 'tinymce/plugins/save';
import 'tinymce/plugins/searchreplace';
import 'tinymce/plugins/spellchecker';
import 'tinymce/plugins/tabfocus';
// import 'tinymce/plugins/table';
import 'tinymce/plugins/template';
import 'tinymce/plugins/textpattern';
import 'tinymce/plugins/visualblocks';
import 'tinymce/plugins/visualchars';
import 'tinymce/plugins/wordcount';

import { isNumber } from '@/utils/is';
import { buildShortUUID } from '@/utils/uuid';
import { onMountedOrActivated } from '@/hooks/vue/onMountedOrActivated';
import { useAppStore } from '@/store/modules/app';

const emits = defineEmits(['inited', 'init-error', 'update:modelValue', 'change'])

const props = defineProps({
  options: {
    type: Object,
    default: () => ({}),
  },
  value: {
    type: String,
  },
  toolbar: {
    type: Array,
    default: [
      'fontsizeselect lineheight searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent  blockquote undo redo removeformat subscript superscript code codesample',
      'hr bullist numlist link  preview anchor pagebreak insertdatetime media  forecolor backcolor fullscreen',
    ],
  },
  plugins: {
    type: Array,
    default: [
      'advlist anchor autolink autosave code codesample  directionality  fullscreen hr insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus  template  textpattern visualblocks visualchars wordcount',
    ],
  },
  modelValue: {
    type: String,
  },
  height: {
    type: [Number, String],
    required: false,
    default: 400,
  },
  width: {
    type: [Number, String],
    required: false,
    default: 'auto',
  },
})

const tinymceId = ref();

const appStore = useAppStore();

const containerWidth = computed(() => {
  const width = props.width;
  if (isNumber(width)) {
    return `${width}px`;
  }
  return width;
});

const skinName = computed(() => {
  return appStore.dark ? 'oxide-dark' : 'oxide';
});

const langName = ref('zh_CN');
const editorRef = ref(null);
const elRef = ref(null);

const attrs = useAttrs()

const initOptions = computed(() => {
  const { height, options, toolbar, plugins } = props;
  const publicPath = import.meta.env.VITE_PUBLIC_PATH || '/';
  return {
    selector: `#${unref(tinymceId)}`,
    height,
    toolbar,
    menubar: 'file edit insert view format table',
    plugins,
    language_url: publicPath + 'resource/tinymce/langs/' + langName.value + '.js',
    language: langName.value,
    branding: false,
    default_link_target: '_blank',
    link_title: false,
    object_resizing: false,
    auto_focus: true,
    skin: skinName.value,
    skin_url: publicPath + 'resource/tinymce/skins/ui/' + skinName.value,
    content_css:
      publicPath + 'resource/tinymce/skins/ui/' + skinName.value + '/content.min.css',
    ...options,
    setup: (editor) => {
      editorRef.value = editor;
      editor.on('init', (e) => initSetup(e));
    },
  }
});

const disabled = computed(() => {
  const { options } = props;
  const getDisabled = options && Reflect.get(options, 'readonly');
  const editor = unref(editorRef);
  if (editor) {
    editor.setMode(getDisabled ? 'readonly' : 'design');
  }
  return getDisabled ?? false;
});

watch(
  () => attrs.disabled,
  () => {
    const editor = unref(editorRef);
    if (!editor) {
      return;
    }
    editor.setMode(attrs.disabled ? 'readonly' : 'design');
  },
);

onMountedOrActivated(() => {
  if (!initOptions.value.inline) {
    tinymceId.value = buildShortUUID('tiny-vue');
  }
  nextTick(() => {
    setTimeout(() => {
      initEditor();
    }, 30);
  });
})

onBeforeUnmount(() => {
  destroy();
});

onDeactivated(() => {
  destroy();
});

function destroy() {
  if (tinymce !== null) {
    tinymce.remove(unref(initOptions).selector);
  }
}

function initEditor() {
  const el = unref(elRef);
  if (el) {
    el.style.visibility = '';
  }
  tinymce
    .init(unref(initOptions))
    .then((editor) => {
      emits('inited', editor);
    })
    .catch((err) => {
      emits('init-error', err);
    });
}

function initSetup(e) {
  const editor = unref(editorRef);
  if (!editor) {
    return;
  }
  const value = props.modelValue || '';

  editor.setContent(value);
  bindModelHandlers(editor);
  bindHandlers(e, attrs, unref(editorRef));
}

function setValue(editor, val, prevVal) {
  if (
    editor &&
    typeof val === 'string' &&
    val !== prevVal &&
    val !== editor.getContent({ format: attrs.outputFormat })
  ) {
    editor.setContent(val);
  }
}

function bindModelHandlers(editor) {
  const modelEvents = attrs.modelEvents ? attrs.modelEvents : null;
  const normalizedEvents = Array.isArray(modelEvents) ? modelEvents.join(' ') : modelEvents;

  watch(
    () => props.modelValue,
    (val, prevVal) => {
      setValue(editor, val, prevVal);
    },
  );

  watch(
    () => props.value,
    (val, prevVal) => {
      setValue(editor, val, prevVal);
    },
    {
      immediate: true,
    },
  );

  editor.on(normalizedEvents ? normalizedEvents : 'change keyup undo redo', () => {
    const content = editor.getContent({ format: attrs.outputFormat });
    emits('update:modelValue', content);
    emits('change', content);
  });

  editor.on('FullscreenStateChanged', (e) => {
    fullscreen.value = e.state;
  });
}

const bindHandlers = (initEvent, listeners, editor) => {
  const validEvents = [
    'onActivate',
    'onAddUndo',
    'onBeforeAddUndo',
    'onBeforeExecCommand',
    'onBeforeGetContent',
    'onBeforeRenderUI',
    'onBeforeSetContent',
    'onBeforePaste',
    'onBlur',
    'onChange',
    'onClearUndos',
    'onClick',
    'onContextMenu',
    'onCopy',
    'onCut',
    'onDblclick',
    'onDeactivate',
    'onDirty',
    'onDrag',
    'onDragDrop',
    'onDragEnd',
    'onDragGesture',
    'onDragOver',
    'onDrop',
    'onExecCommand',
    'onFocus',
    'onFocusIn',
    'onFocusOut',
    'onGetContent',
    'onHide',
    'onInit',
    'onKeyDown',
    'onKeyPress',
    'onKeyUp',
    'onLoadContent',
    'onMouseDown',
    'onMouseEnter',
    'onMouseLeave',
    'onMouseMove',
    'onMouseOut',
    'onMouseOver',
    'onMouseUp',
    'onNodeChange',
    'onObjectResizeStart',
    'onObjectResized',
    'onObjectSelected',
    'onPaste',
    'onPostProcess',
    'onPostRender',
    'onPreProcess',
    'onProgressState',
    'onRedo',
    'onRemove',
    'onReset',
    'onSaveContent',
    'onSelectionChange',
    'onSetAttrib',
    'onSetContent',
    'onShow',
    'onSubmit',
    'onUndo',
    'onVisualAid',
  ];
  const isValidKey = (key) => validEvents.indexOf(key) !== -1;
  Object.keys(listeners)
    .filter(isValidKey)
    .forEach((key) => {
      const handler = listeners[key];
      if (typeof handler === 'function') {
        if (key === 'onInit') {
          handler(initEvent, editor);
        } else {
          editor.on(key.substring(2), (e) => handler(e, editor));
        }
      }
    });
};


</script>

<template>
  <textarea :id="tinymceId" ref="elRef" :style="{ visibility: 'hidden' }" v-if="!initOptions.inline">
  </textarea>
</template>

<style lang="less" scoped></style>